package cn.edu.fzu.homemaking.helper;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

/**
 * 为什么要使用DFA?直接使用String.contains()不行吗？
 * 在数据库中关键词不多时，其实使用contains效率更高，但是一旦数据库中的关键词数量多起来，那么dfa的优势就很明显了
 */
public class DFAHelper {

    private HashMap<String, Object> dfaMap;

    public static final int         MIN_MATCH_TYPE = 1;

    public static final int         MAX_MATCH_TYPE = 2;

    /*
     * {日= {本= {人={isEnd=1}, 鬼= {子={isEnd=1}, isEnd=0}, isEnd=0}, isEnd=0}, 大= {汉=
     * {民={isEnd=0, 族={isEnd=1}}, isEnd=0}, isEnd=0, 中={isEnd=0, 华={isEnd=1,
     * 帝={isEnd=0, 国={isEnd=1}}}}}}
     */
    /** set作为敏感词，创建出对应的dfa的Map，以供检验敏感词 **/
    @SuppressWarnings("unchecked")
    public void createDFAHashMap(Set<String> set) {
        HashMap<String, Object> nowMap;
        //根据set的大小，创建map的大小
        dfaMap = new HashMap<>(set.size());
        //对set里的字符串进行循环
        for (String key : set) {
            //对每个字符串最初，nowMap就是dfaMap
            nowMap = dfaMap;
            for (int i = 0; i < key.length(); i++) {
                //一个个字符循环
                String nowChar = String.valueOf(key.charAt(i));
                //根据nowChar得到nowMap里面对应的value
                HashMap<String, Object> map = (HashMap<String, Object>) nowMap.get(nowChar);
                //如果map为空，则说明nowMap里面没有以nowChar开头的东西，则创建一个新的hashmap，
                //以nowChar为key，新的map为value，放入nowMap
                if (map == null) {
                    map = new HashMap<String, Object>();
                    nowMap.put(nowChar, map);
                }
                //nowMap=map，就是nowChar对应的对象
                nowMap = map;
                //最后在nowMap里设置isEnd
                //如果nowMap里面已经有isEnd，并且为true，说明以前已经有关键字了，就不再设置isEnd
                //因为如果没有这一步，大中华和大中华帝国，先设置大中华
                //在大中华帝国设置的时候，华对应的map有isEnd=true，如果这时对它覆盖，就会isEnd=false，导致大中华这个关键字失效
                if (nowMap.containsKey("isEnd") && (boolean) nowMap.get("isEnd")) {
                    continue;
                }

                nowMap.put("isEnd", i == (key.length() - 1));

            }
        }
    }


    /**
     * 用创建的dfaMap，根据matchType检验字符串string是否包含敏感词，返回包含所有对于敏感词的set
     * 
     * @param string 要检查是否有敏感词在内的字符串
     * @param matchType 检查类型，如大中华帝国牛逼对应大中华和大中华帝国两个关键字，1为最小检查，会检查出大中华，2位最大，会检查出大中华帝国
     * @return 包含的关键词
     */
    public Set<String> getSensitiveWordByDFAMap(String string, int matchType) {
        Set<String> set = new HashSet<>();
        for (int i = 0; i < string.length(); i++) {
            //matchType是针对同一个begin的后面，在同一个begin匹配最长的还是最短的敏感词
            int length = getSensitiveLengthByDFAMap(string, i, matchType);
            if (length > 0) {
                set.add(string.substring(i, i + length));
            }
        }
        return set;
    }


    /** 如果存在，则返回敏感词字符的长度，不存在返回0 **/
    @SuppressWarnings("unchecked")
    private int getSensitiveLengthByDFAMap(String string, int beginIndex, int matchType) {
        //当前匹配的长度
        int nowLength = 0;
        //最终匹配敏感词的长度，因为匹配规则2，如果大中华帝，对应大中华，大中华帝国，在华的时候，nowLength=3，因为是最后一个字，将nowLenth赋给resultLength
        //然后在帝的时候，now=4，result=3，然后不匹配，resultLength就是上一次最大匹配的敏感词的长度
        int resultLength = 0;
        HashMap<String, Object> nowMap = dfaMap;
        for (int i = beginIndex; i < string.length(); i++) {
            String nowChar = String.valueOf(string.charAt(i));
            //根据nowChar得到对应的map，并赋值给nowMap
            nowMap = (HashMap<String, Object>) nowMap.get(nowChar);
            //nowMap里面没有这个char，说明不匹配，直接返回
            if (nowMap == null) {
                break;
            }
            nowLength++;
            //如果现在是最后一个，更新resultLength
            if ((boolean) (nowMap.get("isEnd"))) {
                resultLength = nowLength;
                //如果匹配模式是最小，直接匹配到，退出
                //匹配模式是最大，则继续匹配，resultLength保留上一次匹配到的length
                if (matchType == MIN_MATCH_TYPE) {
                    break;
                }
            }

        }
        return resultLength;
    }

}
